#include "multicast_sender.hpp"
#include <asio.hpp>
#include <iostream>
#include <vector>

#include "result.hpp"

class MulticastSender::Impl
{
public:
    explicit Impl()
    {
    }

    cpp::Result<> init(const std::string& multicast_address, uint16_t port,
                       const std::string& interface_address = "0.0.0.0")
    {
        if (sp_socket != nullptr)
        {
            return cpp::Result<>::Err("Sock is init.");
        }
        endpoint = asio::ip::udp::endpoint(asio::ip::make_address_v4(multicast_address), port);
        sp_socket = std::make_shared<asio::ip::udp::socket>(io_context);
        asio::error_code ec;
        if (sp_socket->open(asio::ip::udp::v4(), ec))
        {
            return cpp::Result<>::Err(ec.message());
        }
        if (sp_socket->set_option(asio::ip::udp::socket::reuse_address(true), ec))
        {
            return cpp::Result<>::Err(ec.message());
        }
        // 设置 TTL
        if (sp_socket->set_option(asio::ip::multicast::hops(32), ec))
        {
            return cpp::Result<>::Err(ec.message());
        }
        return cpp::Result<>::Ok();
    }

    cpp::Result<> send(const std::vector<uint8_t>& data)
    {
        if (sp_socket == nullptr)
        {
            return cpp::Result<>::Err("Sock is no init.");
        }
        asio::error_code ec;
        sp_socket->send_to(asio::buffer(data), endpoint, 0, ec);
        if (ec)
        {
            return cpp::Result<>::Err(ec.message());
        }
        return cpp::Result<>::Ok();
    }

    cpp::Result<> release()
    {
        if (sp_socket == nullptr)
        {
            return cpp::Result<>::Err("Sock is no init.");
        }
        if (io_context.stopped() == false)
        {
            io_context.stop();
        }
        asio::error_code ec;
        if (sp_socket->is_open())
        {
            if (sp_socket->close(ec))
            {
                std::cout << ec.message() << std::endl;
            }
            else
            {
                return cpp::Result<>::Err(ec.message());
            }
            sp_socket.reset();
        }
        return cpp::Result<>::Ok();
    }

private:
    asio::io_context io_context;
    std::shared_ptr<asio::ip::udp::socket> sp_socket;
    asio::ip::udp::endpoint endpoint;
};

MulticastSender::MulticastSender()
{
    pimpl = std::make_unique<Impl>();
}

MulticastSender::~MulticastSender() = default;

cpp::Result<> MulticastSender::init(const std::string& multicast_address, uint16_t port, const std::string& interface_address) const
{
    return pimpl->init(multicast_address, port, interface_address);
}

cpp::Result<> MulticastSender::release() const
{
    return pimpl->release();
}

cpp::Result<> MulticastSender::send(const std::vector<uint8_t>& data) const
{
    return pimpl->send(data);
}
